
# coding: utf-8

# In[ ]:


# 预测某一首歌会被用户收听

from __future__ import division  

import pandas as pd
import numpy as np

class  Song_based_CF:
   
    def __init__(self, X):  
        self.X = X  #评分表
        self.mu = np.mean(self.X[:,3])  #先计算出平均打分的值

        self.SongsForUser={}   #用户听过的所有歌曲
        self.UsersForSong={}   #听过某一首歌的所有用户
        
        for i in range(self.X.shape[0]):  
            uid=self.X[i][0]  #user id
            song_id=self.X[i][1] #song_id 
            listen_count=self.X[i][2]  #listen_count
            
            self.UsersForSong.setdefault(song_id,{})  
            self.SongsForUser.setdefault(uid,{}) 
            
            self.UsersForSong[song_id][uid]=listen_count 
            self.SongsForUser[uid][song_id]=listen_count
            
            
            
        pass  
    
        n_Songs = len(self.UsersForSong)+1 #数组的索引从0开始，浪费第0个元素
        print( n_Songs-1)
        self.similarity = np.zeros((n_Songs, n_Songs), dtype=np.float)
        self.similarity[:,:] = -1
           
    
    #计算song s_id1和s_id2之间的相似性
    def sim_cal(self, song_id1, song_id2):
        if self.similarity[song_id1][song_id2]!=-1:  #如果已经计算好
            return self.similarity[song_id1][song_id2]  
        
        si={}  
        for user in self.UsersForSong[song_id1]:  
            if user in self.UsersForSong[song_id2]:  
               
                si[user]=1  #user为一个有效用用户
        
        #print si
        n=len(si)  
        if (n==0):  
            self.similarity[song_id1][song_id2]=0  
            self.similarity[song_id1][song_id1]=0  
            return 0  
        
        #所有有效用户对Item1的打分
        s1=np.array([self.UsersForSong[song_id1][u] for u in si])  
        
        #所有有效用户对Item2的打分
        s2=np.array([self.UsersForSong[song_id2][u] for u in si])  
        
        sum1=np.sum(s1)  
        sum2=np.sum(s2)  
        sum1Sq=np.sum(s1**2)  
        sum2Sq=np.sum(s2**2)  
        pSum=np.sum(s1*s2)  
        
        #分子
        num=pSum-(sum1*sum2/n)  
        
        #分母
        den=np.sqrt((sum1Sq-sum1**2/n)*(sum2Sq-sum2**2/n))  
        if den==0:  
            self.similarity[song_id1][song_id2]=0  
            self.similarity[song_id2][song_id1]=0  
            return 0  
        
        self.similarity[song_id1][song_id2]=num/den  
        self.similarity[song_id2][song_id1]=num/den  
        return num/den  
            
    #预测用户uid对song song_id的收听次数
    def pred(self,uid,song_id):  
        sim_accumulate=0.0  
        listen_count=0.0  
        
        if(i_id == 599):    
            print(self.UsersForSong[song_id]) 
            
        for item in self.SongsForUser[uid]:  #用户uid打过分的所有Item
            sim = self.sim_cal(song,song_id)    #该Item与i_id之间的相似度
            if sim<0:continue  
           
            
            listen_count += sim * self.SongsForUser[uid][song]  
            sim_accumulate += sim  
        
        #print listen_count,sim_accumulate  
        if sim_accumulate==0: 
            return  self.mu  
        return listen_count/sim_accumulate  
    
# 测试模型的准确率和召回率
    def test(self,test_X):  
        test_X=np.array(test_X) 
        output=[]  
        sums=0  
        print("the test data size is ",test_X.shape) 
        for i in range(test_X.shape[0]):  
            uid = test_X[i][0]  #user id
            song_id = test_X[i][1] #song_id 
        
            #设置默认值，否则用户或item没在训练集中出现时会报错
            self.UsersForSong.setdefault(song_id,{})  
            self.SongsForUser.setdefault(uid,{})
            
            pre=self.pred(uid, song_id)  
            output.append(pre)  
            #print pre,test_X[i][2]  
            sums += (pre-test_X[i][2])**2  
        rmse=np.sqrt(sums/test_X.shape[0])  
        print("the rmse on test data is ",rmse) 
        return output  

